package com.cs.framework.jdbc.mybatis3.executor.resultset;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import com.cs.framework.common.util.StringUtils;

public class SelectResultSetHandler {

	private MappedStatement mappedStatement;

	private Configuration configuration;

	private ObjectFactory objectFactory;

	private TypeHandlerRegistry typeHandlerRegistry;

	private Class<?> resultType;

	private final Map<CacheKey, Object> nestedResultObjects = new HashMap<CacheKey, Object>();

	public SelectResultSetHandler(MappedStatement mappedStatement) {
		this.mappedStatement = mappedStatement;
		this.configuration = mappedStatement.getConfiguration();
		this.objectFactory = configuration.getObjectFactory();
		this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
	}

	/**
	 * 处理结果
	 * 
	 * @param stmt
	 * @return
	 * @throws SQLException
	 * @throws ClassNotFoundException
	 */
	@SuppressWarnings("unchecked")
	public <T> List<T> handleResultSets(Statement stmt, Class<T> resultType) throws SQLException,
			ClassNotFoundException {
		this.resultType = resultType;

		List<Object> multipleResults = new ArrayList<Object>();

		ResultSetWrapper rsw = getFirstResultSet(stmt);

		handleResultSet(rsw, multipleResults);
		rsw = getNextResultSet(stmt);
		cleanUpAfterHandlingResultSet();

		return (List<T>) collapseSingleResultList(multipleResults);
	}

	private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
		ResultSet rs = stmt.getResultSet();
		while (rs == null) {
			if (stmt.getMoreResults()) {
				rs = stmt.getResultSet();
			} else {
				if (stmt.getUpdateCount() == -1) {
					break;
				}
			}
		}
		return rs != null ? new ResultSetWrapper(rs, configuration) : null;
	}

	private ResultSetWrapper getNextResultSet(Statement stmt) throws SQLException {
		try {
			if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
				if (!((!stmt.getMoreResults()) && (stmt.getUpdateCount() == -1))) {
					ResultSet rs = stmt.getResultSet();
					return rs != null ? new ResultSetWrapper(rs, configuration) : null;
				}
			}
		} catch (Exception e) {
		}
		return null;
	}

	private void closeResultSet(ResultSet rs) {
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
		}
	}

	private void cleanUpAfterHandlingResultSet() {
		nestedResultObjects.clear();
	}

	private void handleResultSet(ResultSetWrapper rsw, List<Object> multipleResults) throws SQLException,
			ClassNotFoundException {
		try {
			DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
			handleRowValues(rsw, defaultResultHandler);
			multipleResults.add(defaultResultHandler.getResultList());
		} finally {
			closeResultSet(rsw.getResultSet());
		}
	}

	private List<Object> collapseSingleResultList(List<Object> multipleResults) {
		if (multipleResults.size() == 1) {
			@SuppressWarnings("unchecked")
			List<Object> returned = (List<Object>) multipleResults.get(0);
			return returned;
		}
		return multipleResults;
	}

	private void handleRowValues(ResultSetWrapper rsw, ResultHandler resultHandler) throws SQLException,
			ClassNotFoundException {
		DefaultResultContext resultContext = new DefaultResultContext();
		while (shouldProcessMoreRows(rsw.getResultSet(), resultContext)) {
			Object rowValue = getRowValue(rsw);
			resultContext.nextResultObject(rowValue);
			resultHandler.handleResult(resultContext);
		}
	}

	private boolean shouldProcessMoreRows(ResultSet rs, ResultContext context) throws SQLException {
		return !context.isStopped() && rs.next();
	}

	private Object getRowValue(ResultSetWrapper rsw) throws SQLException, ClassNotFoundException {
		Object resultObject = createResultObject(rsw);
		if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(this.resultType)) {
			final MetaObject metaObject = configuration.newMetaObject(resultObject);
			boolean foundValues = false;
			foundValues = applyAutomaticMappings(rsw, metaObject) || foundValues;
			resultObject = foundValues ? resultObject : null;
			return resultObject;
		}
		return resultObject;
	}

	private boolean applyAutomaticMappings(ResultSetWrapper rsw, MetaObject metaObject) throws SQLException,
			ClassNotFoundException {
		final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(mappedStatement.getId());
		boolean foundValues = false;
		for (String columnName : unmappedColumnNames) {
			String propertyName = StringUtils.replace(columnName, "_", "");

			final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
			if (property != null && metaObject.hasSetter(property)) {
				final Class<?> propertyType = metaObject.getSetterType(property);
				if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
					final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
					final Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
					if (value != null || configuration.isCallSettersOnNulls()) { // issue
						if (value != null || !propertyType.isPrimitive()) {
							metaObject.setValue(property, value);
						}
						foundValues = true;
					}
				}
			}
		}
		return foundValues;
	}

	private Object createResultObject(ResultSetWrapper rsw) throws SQLException, ClassNotFoundException {
		final Class<?> resultType = this.resultType;
		if (typeHandlerRegistry.hasTypeHandler(resultType)) {
			return createPrimitiveResultObject(rsw);
		} else {
			return objectFactory.create(resultType);
		}
	}

	private Object createPrimitiveResultObject(ResultSetWrapper rsw) throws SQLException, ClassNotFoundException {
		final String columnName = rsw.getColumnNames().get(0);
		final TypeHandler<?> typeHandler = rsw.getTypeHandler(this.resultType, columnName);
		return typeHandler.getResult(rsw.getResultSet(), columnName);
	}

}
